﻿using AdvVerifyModels.Clients;
using AdvVerifyModels.Models.Responses.SubstrateTracking;
using AdvVerifyModels.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.ServerSentEvents;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace AdvVerifyModels.SubstrateTracking
{
    public class SubstrateTrackingSubscription(SubstrateTrackingResult result,
ISubscription<SubstrateTrackingEvent> sub) : IDisposable
    {
        public SubstrateTrackingResult Result => result;

        public void Dispose()
        {
            sub.Dispose();
        }

        public ValueTask<SubstrateTrackingEvent> Read(CancellationToken tok = default)
        {
            return sub.Read(tok);
        }
    }

    public class SubstrateTrackingEventSource(IWaferServiceClient client,
        IEventServiceClient eventService)
    {
        readonly object lockObj = new();
        readonly MessageQueue<SubstrateTrackingEvent> queue = new();

        Task<SubstrateTrackingResult>? trackingTask;

        public async Task<SubstrateTrackingSubscription> Subscribe()
        {
            try
            {
                var result = await Track().ConfigureAwait(false);
                return new SubstrateTrackingSubscription(result, queue.Subscribe(50));
            }
            catch (Exception)
            {
                lock (lockObj)
                {
                    trackingTask = null;
                }
                throw;
            }
        }

        Task<SubstrateTrackingResult> Track()
        {
            lock (lockObj)
            {
                trackingTask ??= Task.Run(async () =>
                {
                    var result = await client.Track().ConfigureAwait(false);
                    _ = ReadEvents(result.LastEventId);
                    return result;
                });

                return trackingTask;
            }
        }

        async Task ReadEvents(string id)
        {
            var lastId = id;
            var exceptions = new List<Exception>();
            for (; ; )
            {
                try
                {
                    using Stream stream = await eventService.GetWaferTransferEvents(lastId);
                    await foreach (SseItem<SubstrateTrackingEvent?> item in SseParser.Create(stream, (eventType, bytes) =>
                        JsonSerializer.Deserialize<SubstrateTrackingEvent>(bytes)).EnumerateAsync().ConfigureAwait(false))
                    {
                        var e = item.Data!;
                        queue.Send(e);
                        lastId = e.Id;
                    }
                }
                catch (Exception ex)
                {
                    if (lastId != id)
                    {
                        exceptions.Clear();
                    }

                    exceptions.Add(ex);

                    if (exceptions.Count >= 3)
                    {
                        lock (lockObj)
                        {
                            trackingTask = null;
                        }

                        queue.UnsubscribeAll(ex);
                        throw new AggregateException("event stream failed", exceptions);
                    }
                }
            }
        }
    }
}
